package org.gaaidou.ptarmigan.springboot.core.common;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


public final class ClazzUtil {
    private static final Map<Method, String> METHOD_NAME_CACHE = new ConcurrentHashMap<>();
    private static final Map<Method, Boolean> METHOD_CACHE_ANNOTATION_STATE = new ConcurrentHashMap<>();

    public static String getMethodName(Method method) {
        if (method == null) {
            return null;
        }
        if (METHOD_NAME_CACHE.containsKey(method)) {
            return METHOD_NAME_CACHE.get(method);
        }
        Class<?> clazz = method.getDeclaringClass();
        String methodName = method.getName();
        Class<?>[] types = method.getParameterTypes();
        String pName = Arrays.stream(types).map(Class::getSimpleName).collect(Collectors.joining(","));
        String name = String.format("%s.%s(%s)", clazz.getName(), methodName, pName);
        METHOD_NAME_CACHE.putIfAbsent(method, name);
        return name;
    }

    public static boolean hasAnnotation(Method method, Class<? extends Annotation> annotation) {
        if (method == null) {
            return false;
        }
        if (METHOD_CACHE_ANNOTATION_STATE.containsKey(method)) {
            return METHOD_CACHE_ANNOTATION_STATE.get(method);
        }
        Class<?> targetClass = method.getDeclaringClass();
        Annotation clazzAnnotation = AnnotationUtils.findAnnotation(targetClass, annotation);
        Annotation methodAnnotation = AnnotationUtils.findAnnotation(method, annotation);
        boolean state = methodAnnotation != null || clazzAnnotation != null;
        METHOD_CACHE_ANNOTATION_STATE.putIfAbsent(method, state);
        return state;
    }

    public static List<Class<?>> getClazzListByPackage(String packageName) throws Exception {
        List<Class<?>> clazzList = new LinkedList<>();
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metaReader = new CachingMetadataReaderFactory();
        Resource[] resources = resolver.getResources(String.format("classpath*:%s/**/*.class", packageName.replace(".", "/")));
        ClassLoader loader = Thread.currentThread().getContextClassLoader();

        for (Resource resource : resources) {
            MetadataReader reader = metaReader.getMetadataReader(resource);
            String className = reader.getClassMetadata().getClassName();
            Class<?> clazz = loader.loadClass(className);
            clazzList.add(clazz);
        }
        return clazzList;
    }

    public static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        Class<?> tempClass = clazz;
        while (tempClass != null) {
            fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
            tempClass = tempClass.getSuperclass();
        }
        return fieldList;
    }
}